概况

本程序读取GRAPES_1km数值模式预报数据, 叠加在三维地形上进行可视化显示.

程序配置

载入必备的程序库用于读取, 处理和可视化数据.

library(ggplot2)
Use suppressPackageStartupMessages() to eliminate package startup messages
library(raster)
library(png)
library(rayshader)
library(leaflet)
library(metR)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
library(geosphere)
library(magrittr)

载入程辑包:‘magrittr’

The following object is masked from ‘package:raster’:

    extract
library(nmcMetIO)

work_dir <- "/media/kan-dai/workspace/workcode/personal/dk_winter_olympic_3D"
setwd(work_dir)

准备地理信息数据

准备地形图像

地形数据从"https://www.gmrt.org/services/gridserverinfo.php#!/services/getGMRTGrid" 下载, 输出为geotiff格式, 分辨率选择最大.


# 载入地形数据
elev_file <- "../data/stadium_topo_small.tif"
elev_img <- raster::raster(elev_file)

# 设置坐标信息
crs(elev_img) <- CRS('+init=EPSG:4326')

# 利用leaflet可视化地形数据
pal <- leaflet::colorNumeric(c("#0C2C84", "#41B6C4", "#FFFFCC"), # span of values in palette
                    values(elev_img), #range of values
  na.color = "transparent")

leaflet() %>% 
  addProviderTiles("OpenTopoMap", group = "OSM") %>%
  addRasterImage(elev_img,
                 color=pal, 
                 opacity = 0.6) %>%
  leaflet::addLegend(values = values(elev_img),
                     pal = pal,
                     title = "Digital elevation model")
  • 将raster类型数据转换为rayshader可处理的数据;
  • 由于数据内可能存在NA值, rayshader无法处理, 可以用最低值来填充.

# convert it to a matrix which rayshader can handle.
elev_matrix <- raster_to_matrix(elev_img)
[1] "Dimensions of matrix are: 1058x906."
# fill NA values
elev_matrix[!is.finite(elev_matrix)] <- min(elev_matrix, na.rm=TRUE)

计算地形阴影层, 存入变量, 可以在三维地形显示时叠加到地形上. 将地形数据和阴影层叠加起来, 展示二维效果.


shademat_file <- file.path(work_dir, 'data', "shademat_small.rds")
ambmat_file <- file.path(work_dir, 'data', "ambmat_small.rds")
raymat_file <- file.path(work_dir, 'data', "raymat_small.rds")

if(!file.exists(shademat_file)){
  shademat <- sphere_shade(elev_matrix, texture = "imhof4")
  saveRDS(shademat, file=shademat_file)}
shademat <- readRDS(shademat_file)

if(!file.exists(ambmat_file)){
  ambmat <- ambient_shade(elev_matrix, zscale=30)
  saveRDS(ambmat, file=ambmat_file)}
ambmat <- readRDS(ambmat_file)

if(!file.exists(raymat_file)){
  raymat <- ray_shade(elev_matrix, zscale=30, lambert = TRUE)
  saveRDS(raymat, file=raymat_file)}
raymat <- readRDS(raymat_file)

# plot 2D
shademat %>%
  add_shadow(raymat, max_darken = 0.5) %>%
  add_shadow(ambmat, max_darken = 0.5) %>%
  plot_map()

准备地图图像


# 获得地图范围
bbox = extent(elev_img)
bbox = list(p1 = list(long = bbox[1], lat = bbox[3]),
            p2 = list(long = bbox[2], lat = bbox[4]))

# get map image size
image_size <- define_image_size(bbox, major_dim = 2000)

overlay_file <- "../data/stadium_map_small.png"
if(!file.exists(overlay_file)){
  get_arcgis_map_image(bbox, map_type = "World_Topo_Map", file = overlay_file,
                       width = image_size$width, height = image_size$height, 
                       sr_bbox=4326)
}
载入需要的程辑包:httr
载入需要的程辑包:glue

载入程辑包:‘glue’

The following object is masked from ‘package:raster’:

    trim

载入需要的程辑包:jsonlite
{
  "results": [
    {
      "paramName": "Output_File",
      "dataType": "GPDataFile",
      "value": {
        "url": "https://utility.arcgisonline.com/arcgis/rest/directories/arcgisoutput/Utilities/PrintingTools_GPServer/x_____xsJm_6ylOoAUhl7bzx4mC4w..x_____x_ags_cb2a025a-7e61-11ec-b522-22000a88f94a.png"
      }
    }
  ],
  "messages": []
}
image saved to file: ../data/stadium_map_small.png
overlay_img <- png::readPNG(overlay_file)
plot.new()

# 显示png文件
rasterImage(overlay_img, 0,0,1,1)

准备高分辨率网格预报数据

读入温度场数据


# load forecast
#datafile <- "/media/winter_olympic_data/cma_meso_1km/202201220800/t2m/202201220800.001"
#data <- read_micaps_4(datafile, outList=FALSE)
dataT <- retrieve_micaps_model_grid("GRAPES_1KM/TMP/2M_ABOVE_GROUND/", filter="*.001")

# extract region
dataT <- dataT[lon >= bbox$p1$long & lon <= bbox$p2$long & lat >= bbox$p1$lat & lat <= bbox$p2$lat]

# smooth the data
out <- smooth2d(dataT$lon, dataT$lat, dataT$var1, theta=0.02, nx=128, ny=128)

# plot contour image
colors <- c("#3D0239","#FA00FC","#090079","#5E9DF8","#2E5E7F",
            "#06F9FB","#0BF40B","#006103","#FAFB07","#D50404","#5A0303")
p_temp <- ggplot(out, aes(x, y, z=z)) +
  # geom_contour_fill(breaks=seq(-12, 10, by=0.2)) +
  geom_contour(breaks=seq(-12, 10, by=0.2), color="black", size=0.5) + 
  geom_text_contour(stroke = 0.3, size=16, min.size=3) +
  scale_fill_gradientn(name="Temp", colours=colors, limits=c(-12, 5)) +
  scale_x_continuous(expand=c(0,0)) +
  scale_y_continuous(expand=c(0,0)) +
  guides(fill=FALSE)+
  theme(panel.spacing=grid::unit(0, "mm"),
        plot.margin=grid::unit(rep(-1.25,4),"lines"),
        panel.background = element_rect(fill = "transparent"), # bg of the panel
        plot.background = element_rect(fill = "transparent", color = NA), # bg of the plot
        axis.text=element_blank(),
        axis.ticks=element_blank(),
        axis.title=element_blank(),
        legend.key.size = unit(5, "cm"),
        legend.key.height = unit(2.5, 'cm'),
        legend.key.width = unit(6, 'cm'),
        legend.position = c(0.25, 0.06),
        legend.direction="horizontal",
        legend.text = element_text(size=32),
        legend.title = element_text(size=32),
        legend.background = element_rect(fill="lightblue", 
                                         size=0.5, linetype="solid"))

# export png file
overlay_file_temp <- "../data/fine_gridded_forecast_temp.png"
png(overlay_file_temp, width=image_size$width, height=image_size$height, units="px", bg="transparent")
p_temp
dev.off()
null device 
          1 
img <- png::readPNG(overlay_file_temp)
plot.new()
rasterImage(img, 0,0,1,1)

colors <- c("#3D0239","#FA00FC","#090079","#5E9DF8","#2E5E7F",
            "#06F9FB","#0BF40B","#006103","#FAFB07","#D50404","#5A0303")
p_temp <- ggplot(out, aes(x, y, z=z)) +
  geom_contour_fill(breaks=seq(-16, 8, by=0.25)) +
  geom_contour(breaks=seq(-16, 8, by=0.25), color="black", size=0.5) + 
  scale_fill_gradientn(name="Temp", colours=colors, limits=c(-12, 5)) +
  guides() +
  theme(legend.key.size = unit(1, "cm"),
        legend.key.height = unit(0.4, 'cm'),
        legend.key.width = unit(1, 'cm'),
        legend.position = c(0.1, 0.1),
        legend.direction="horizontal",
        legend.background = element_rect(fill="lightblue", 
                                         size=0.5, linetype="solid"))
p_temp

读入风场数据

# load fine gridded forecast
dataU <- retrieve_micaps_model_grid("GRAPES_1KM/UGRD/10M_ABOVE_GROUND/", filter="*.001")
dataV <- retrieve_micaps_model_grid("GRAPES_1KM/VGRD/10M_ABOVE_GROUND/", filter="*.001")

# extract region
dataU <- dataU[lon >= bbox$p1$long & lon <= bbox$p2$long & lat >= bbox$p1$lat & lat <= bbox$p2$lat]
dataV <- dataV[lon >= bbox$p1$long & lon <= bbox$p2$long & lat >= bbox$p1$lat & lat <= bbox$p2$lat]
dataUV <- merge(dataU, dataV, by=c("lon", "lat", "lev", "time", "initTime", "fhour"))

# plot wind streamlines
p_wind <- ggplot(dataUV, aes(lon, lat)) +
  geom_streamline(aes(dx=dlon(dataUV$var1.x,lat), dy=dlat(dataUV$var1.y), size=..step.., 
                      alpha=..step.., color=sqrt(..dx..^2 + ..dy..^2)),
                  L=0.03, res=2, n=10, S=5, lineend="round", size=5, arrow=NULL) + 
  geom_arrow(aes(dx = dataUV$var1.x*0.8, dy = dataUV$var1.y*0.8), 
             skip = 1, color = "black", arrow.length=3, size = 2.5) +
  viridis::scale_color_viridis(guide="none") +
  scale_size(range=c(0,3), guide="none") +
  scale_alpha(guide="none") +
  scale_x_continuous(expand=c(0,0)) +
  scale_y_continuous(expand=c(0,0)) +
  theme(panel.spacing=grid::unit(0, "mm"),
        plot.margin=grid::unit(rep(-1.25,4),"lines"),
        panel.background = element_rect(fill = "transparent"), # bg of the panel
        plot.background = element_rect(fill = "transparent", color = NA), # bg of the plot
        panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        axis.text=element_blank(),
        axis.ticks=element_blank(),
        axis.title=element_blank())

# export png file
overlay_file_wind <- "../data/fine_gridded_forecast_wind.png"
png(overlay_file_wind, width=image_size$width, height=image_size$height, units="px", bg="transparent")
p_wind
dev.off()
null device 
          1 
img <- png::readPNG(overlay_file_wind)
plot.new()
rasterImage(img, 0,0,1,1)

三维地形叠加分析

在地形上叠加上述生成的地图, 气温分布以及风场流线图.


overlay_img <- png::readPNG(overlay_file)
overlay_img_temp <- png::readPNG(overlay_file_temp)
overlay_img_wind <- png::readPNG(overlay_file_wind)

# 2D plot with map overlay
shademat %>%
  add_overlay(overlay_img, alphalayer = 0.95) %>%
  add_overlay(overlay_img_temp, alphalayer = 0.95) %>%
  add_overlay(overlay_img_wind, alphalayer = 0.95) %>%
  add_shadow(raymat, max_darken = 0.4) %>%
  add_shadow(ambmat, max_darken = 0.4) %>%
  plot_map()

显示三维地形图.

zscale <- 10

rgl::clear3d()
shademat %>% 
  add_overlay(overlay_img, alphalayer = 0.95) %>%
  add_overlay(overlay_img_temp, alphalayer = 0.6) %>%
  add_overlay(overlay_img_wind, alphalayer = 0.8) %>%
  add_shadow(raymat, max_darken = 0.4) %>%
  add_shadow(ambmat, max_darken = 0.4) %>%
  plot_3d(elev_matrix, zscale = zscale, windowsize = c(1000, 1000), background = "white", 
          water = FALSE, soliddepth = -150, wateralpha = 0,
          theta = 25, phi = 30, zoom = 0.6, fov = 60)

label <- list(text="Olympic Stadium")
label$pos <- find_image_coordinates(long=116.39200, lat=39.99150, bbox=bbox, 
                                    image_width=dim(elev_matrix)[1], image_height=dim(elev_matrix)[2])
render_label(elev_matrix, x = label$pos$x, y = label$pos$y, z = 6000,
             zscale = zscale, text = label$text, textsize=1.2, linewidth = 5, freetype=TRUE)
dist = distm(c(extent(elev_img)[1], extent(elev_img)[3]), 
             c(extent(elev_img)[1], extent(elev_img)[4]), fun = distHaversine)[,1]
render_scalebar(limits=round(dist/1000.,1),label_unit = 'km')
render_compass(position = "N",compass_radius=120, scale_distance = 1.2) 

rgl::par3d(windowRect=c(-450, -100, 1000, 1100))
rgl::view3d(theta=20, phi=30, fov=45, zoom=0.6)

Sys.sleep(3)
render_snapshot('data/winter_olympic/output_image.png', title_text = "2022北京冬奥精细化预报",
                title_size = 50, title_color = "black", title_font = "SimHei")
# rgl::rgl.close()
LS0tCnRpdGxlOiAi5YyX5Lqs6bif5bei5L2T6IKy6aaG5qih5byP6aKE5oql5LiJ57u05pi+56S65YiG5p6QIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyDmpoLlhrUKCuacrOeoi+W6j+ivu+WPlkdSQVBFU18xa23mlbDlgLzmqKHlvI/pooTmiqXmlbDmja4sIOWPoOWKoOWcqOS4iee7tOWcsOW9ouS4iui/m+ihjOWPr+inhuWMluaYvuekui4KCiMjIOeoi+W6j+mFjee9rgoK6L295YWl5b+F5aSH55qE56iL5bqP5bqT55So5LqO6K+75Y+WLCDlpITnkIblkozlj6/op4bljJbmlbDmja4uCgpgYGB7ciB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocmFzdGVyKQpsaWJyYXJ5KHBuZykKbGlicmFyeShyYXlzaGFkZXIpCmxpYnJhcnkobGVhZmxldCkKbGlicmFyeShtZXRSKQpsaWJyYXJ5KGdlb3NwaGVyZSkKbGlicmFyeShtYWdyaXR0cikKbGlicmFyeShubWNNZXRJTykKCndvcmtfZGlyIDwtICIvbWVkaWEva2FuLWRhaS93b3Jrc3BhY2Uvd29ya2NvZGUvcGVyc29uYWwvZGtfd2ludGVyX29seW1waWNfM0QiCnNldHdkKHdvcmtfZGlyKQpgYGAKCiMjIOWHhuWkh+WcsOeQhuS/oeaBr+aVsOaNrgoKIyMjIOWHhuWkh+WcsOW9ouWbvuWDjwoK5Zyw5b2i5pWw5o2u5LuOIltodHRwczovL3d3dy5nbXJ0Lm9yZy9zZXJ2aWNlcy9ncmlkc2VydmVyaW5mby5waHBcIyEvc2VydmljZXMvZ2V0R01SVEdyaWQiIOS4i+i9vV0oaHR0cHM6Ly93d3cuZ21ydC5vcmcvc2VydmljZXMvZ3JpZHNlcnZlcmluZm8ucGhwIyEvc2VydmljZXMvZ2V0R01SVEdyaWQlMjLkuIvovb0pLCDovpPlh7rkuLpnZW90aWZm5qC85byPLCDliIbovqjnjofpgInmi6nmnIDlpKcuCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKIyDovb3lhaXlnLDlvaLmlbDmja4KZWxldl9maWxlIDwtICIuLi9kYXRhL3N0YWRpdW1fdG9wb19zbWFsbC50aWYiCmVsZXZfaW1nIDwtIHJhc3Rlcjo6cmFzdGVyKGVsZXZfZmlsZSkKCiMg6K6+572u5Z2Q5qCH5L+h5oGvCmNycyhlbGV2X2ltZykgPC0gQ1JTKCcraW5pdD1FUFNHOjQzMjYnKQoKIyDliKnnlKhsZWFmbGV05Y+v6KeG5YyW5Zyw5b2i5pWw5o2uCnBhbCA8LSBsZWFmbGV0Ojpjb2xvck51bWVyaWMoYygiIzBDMkM4NCIsICIjNDFCNkM0IiwgIiNGRkZGQ0MiKSwgIyBzcGFuIG9mIHZhbHVlcyBpbiBwYWxldHRlCiAgICAgICAgICAgICAgICAgICAgdmFsdWVzKGVsZXZfaW1nKSwgI3JhbmdlIG9mIHZhbHVlcwogIG5hLmNvbG9yID0gInRyYW5zcGFyZW50IikKCmxlYWZsZXQoKSAlPiUgCiAgYWRkUHJvdmlkZXJUaWxlcygiT3BlblRvcG9NYXAiLCBncm91cCA9ICJPU00iKSAlPiUKICBhZGRSYXN0ZXJJbWFnZShlbGV2X2ltZywKICAgICAgICAgICAgICAgICBjb2xvcj1wYWwsIAogICAgICAgICAgICAgICAgIG9wYWNpdHkgPSAwLjYpICU+JQogIGxlYWZsZXQ6OmFkZExlZ2VuZCh2YWx1ZXMgPSB2YWx1ZXMoZWxldl9pbWcpLAogICAgICAgICAgICAgICAgICAgICBwYWwgPSBwYWwsCiAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gIkRpZ2l0YWwgZWxldmF0aW9uIG1vZGVsIikKYGBgCgotICAg5bCGcmFzdGVy57G75Z6L5pWw5o2u6L2s5o2i5Li6cmF5c2hhZGVy5Y+v5aSE55CG55qE5pWw5o2uOwotICAg55Sx5LqO5pWw5o2u5YaF5Y+v6IO95a2Y5ZyoTkHlgLwsIHJheXNoYWRlcuaXoOazleWkhOeQhiwg5Y+v5Lul55So5pyA5L2O5YC85p2l5aGr5YWFLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCiMgY29udmVydCBpdCB0byBhIG1hdHJpeCB3aGljaCByYXlzaGFkZXIgY2FuIGhhbmRsZS4KZWxldl9tYXRyaXggPC0gcmFzdGVyX3RvX21hdHJpeChlbGV2X2ltZykKCiMgZmlsbCBOQSB2YWx1ZXMKZWxldl9tYXRyaXhbIWlzLmZpbml0ZShlbGV2X21hdHJpeCldIDwtIG1pbihlbGV2X21hdHJpeCwgbmEucm09VFJVRSkKYGBgCgrorqHnrpflnLDlvaLpmLTlvbHlsYIsIOWtmOWFpeWPmOmHjywg5Y+v5Lul5Zyo5LiJ57u05Zyw5b2i5pi+56S65pe25Y+g5Yqg5Yiw5Zyw5b2i5LiKLiDlsIblnLDlvaLmlbDmja7lkozpmLTlvbHlsYLlj6DliqDotbfmnaUsIOWxleekuuS6jOe7tOaViOaenC4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpzaGFkZW1hdF9maWxlIDwtIGZpbGUucGF0aCh3b3JrX2RpciwgJ2RhdGEnLCAic2hhZGVtYXRfc21hbGwucmRzIikKYW1ibWF0X2ZpbGUgPC0gZmlsZS5wYXRoKHdvcmtfZGlyLCAnZGF0YScsICJhbWJtYXRfc21hbGwucmRzIikKcmF5bWF0X2ZpbGUgPC0gZmlsZS5wYXRoKHdvcmtfZGlyLCAnZGF0YScsICJyYXltYXRfc21hbGwucmRzIikKCmlmKCFmaWxlLmV4aXN0cyhzaGFkZW1hdF9maWxlKSl7CiAgc2hhZGVtYXQgPC0gc3BoZXJlX3NoYWRlKGVsZXZfbWF0cml4LCB0ZXh0dXJlID0gImltaG9mNCIpCiAgc2F2ZVJEUyhzaGFkZW1hdCwgZmlsZT1zaGFkZW1hdF9maWxlKX0Kc2hhZGVtYXQgPC0gcmVhZFJEUyhzaGFkZW1hdF9maWxlKQoKaWYoIWZpbGUuZXhpc3RzKGFtYm1hdF9maWxlKSl7CiAgYW1ibWF0IDwtIGFtYmllbnRfc2hhZGUoZWxldl9tYXRyaXgsIHpzY2FsZT0zMCkKICBzYXZlUkRTKGFtYm1hdCwgZmlsZT1hbWJtYXRfZmlsZSl9CmFtYm1hdCA8LSByZWFkUkRTKGFtYm1hdF9maWxlKQoKaWYoIWZpbGUuZXhpc3RzKHJheW1hdF9maWxlKSl7CiAgcmF5bWF0IDwtIHJheV9zaGFkZShlbGV2X21hdHJpeCwgenNjYWxlPTMwLCBsYW1iZXJ0ID0gVFJVRSkKICBzYXZlUkRTKHJheW1hdCwgZmlsZT1yYXltYXRfZmlsZSl9CnJheW1hdCA8LSByZWFkUkRTKHJheW1hdF9maWxlKQoKIyBwbG90IDJECnNoYWRlbWF0ICU+JQogIGFkZF9zaGFkb3cocmF5bWF0LCBtYXhfZGFya2VuID0gMC41KSAlPiUKICBhZGRfc2hhZG93KGFtYm1hdCwgbWF4X2RhcmtlbiA9IDAuNSkgJT4lCiAgcGxvdF9tYXAoKQpgYGAKCiMjIyDlh4blpIflnLDlm77lm77lg48KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgojIOiOt+W+l+WcsOWbvuiMg+WbtApiYm94ID0gZXh0ZW50KGVsZXZfaW1nKQpiYm94ID0gbGlzdChwMSA9IGxpc3QobG9uZyA9IGJib3hbMV0sIGxhdCA9IGJib3hbM10pLAogICAgICAgICAgICBwMiA9IGxpc3QobG9uZyA9IGJib3hbMl0sIGxhdCA9IGJib3hbNF0pKQoKIyBnZXQgbWFwIGltYWdlIHNpemUKaW1hZ2Vfc2l6ZSA8LSBkZWZpbmVfaW1hZ2Vfc2l6ZShiYm94LCBtYWpvcl9kaW0gPSAyMDAwKQoKb3ZlcmxheV9maWxlIDwtICIuLi9kYXRhL3N0YWRpdW1fbWFwX3NtYWxsLnBuZyIKaWYoIWZpbGUuZXhpc3RzKG92ZXJsYXlfZmlsZSkpewogIGdldF9hcmNnaXNfbWFwX2ltYWdlKGJib3gsIG1hcF90eXBlID0gIldvcmxkX1RvcG9fTWFwIiwgZmlsZSA9IG92ZXJsYXlfZmlsZSwKICAgICAgICAgICAgICAgICAgICAgICB3aWR0aCA9IGltYWdlX3NpemUkd2lkdGgsIGhlaWdodCA9IGltYWdlX3NpemUkaGVpZ2h0LCAKICAgICAgICAgICAgICAgICAgICAgICBzcl9iYm94PTQzMjYpCn0Kb3ZlcmxheV9pbWcgPC0gcG5nOjpyZWFkUE5HKG92ZXJsYXlfZmlsZSkKcGxvdC5uZXcoKQoKIyDmmL7npLpwbmfmlofku7YKcmFzdGVySW1hZ2Uob3ZlcmxheV9pbWcsIDAsMCwxLDEpCmBgYAoKIyMg5YeG5aSH6auY5YiG6L6o546H572R5qC86aKE5oql5pWw5o2uCgojIyMg6K+75YWl5rip5bqm5Zy65pWw5o2uCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKIyBsb2FkIGZvcmVjYXN0CiNkYXRhZmlsZSA8LSAiL21lZGlhL3dpbnRlcl9vbHltcGljX2RhdGEvY21hX21lc29fMWttLzIwMjIwMTIyMDgwMC90Mm0vMjAyMjAxMjIwODAwLjAwMSIKI2RhdGEgPC0gcmVhZF9taWNhcHNfNChkYXRhZmlsZSwgb3V0TGlzdD1GQUxTRSkKZGF0YVQgPC0gcmV0cmlldmVfbWljYXBzX21vZGVsX2dyaWQoIkdSQVBFU18xS00vVE1QLzJNX0FCT1ZFX0dST1VORC8iLCBmaWx0ZXI9IiouMDAxIikKCiMgZXh0cmFjdCByZWdpb24KZGF0YVQgPC0gZGF0YVRbbG9uID49IGJib3gkcDEkbG9uZyAmIGxvbiA8PSBiYm94JHAyJGxvbmcgJiBsYXQgPj0gYmJveCRwMSRsYXQgJiBsYXQgPD0gYmJveCRwMiRsYXRdCgojIHNtb290aCB0aGUgZGF0YQpvdXQgPC0gc21vb3RoMmQoZGF0YVQkbG9uLCBkYXRhVCRsYXQsIGRhdGFUJHZhcjEsIHRoZXRhPTAuMDIsIG54PTEyOCwgbnk9MTI4KQoKIyBwbG90IGNvbnRvdXIgaW1hZ2UKY29sb3JzIDwtIGMoIiMzRDAyMzkiLCIjRkEwMEZDIiwiIzA5MDA3OSIsIiM1RTlERjgiLCIjMkU1RTdGIiwKICAgICAgICAgICAgIiMwNkY5RkIiLCIjMEJGNDBCIiwiIzAwNjEwMyIsIiNGQUZCMDciLCIjRDUwNDA0IiwiIzVBMDMwMyIpCnBfdGVtcCA8LSBnZ3Bsb3Qob3V0LCBhZXMoeCwgeSwgej16KSkgKwogIGdlb21fY29udG91cl9maWxsKGJyZWFrcz1zZXEoLTEyLCAxMCwgYnk9MC4yKSkgKwogIGdlb21fY29udG91cihicmVha3M9c2VxKC0xMiwgMTAsIGJ5PTAuMiksIGNvbG9yPSJibGFjayIsIHNpemU9MC41KSArIAogIGdlb21fdGV4dF9jb250b3VyKHN0cm9rZSA9IDAuMywgc2l6ZT0xNiwgbWluLnNpemU9MykgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKG5hbWU9IlRlbXAiLCBjb2xvdXJzPWNvbG9ycywgbGltaXRzPWMoLTEyLCA1KSkgKwogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQ9YygwLDApKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZD1jKDAsMCkpICsKICBndWlkZXMoZmlsbD1GQUxTRSkrCiAgdGhlbWUocGFuZWwuc3BhY2luZz1ncmlkOjp1bml0KDAsICJtbSIpLAogICAgICAgIHBsb3QubWFyZ2luPWdyaWQ6OnVuaXQocmVwKC0xLjI1LDQpLCJsaW5lcyIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIpLCAjIGJnIG9mIHRoZSBwYW5lbAogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3IgPSBOQSksICMgYmcgb2YgdGhlIHBsb3QKICAgICAgICBheGlzLnRleHQ9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3M9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoNSwgImNtIiksCiAgICAgICAgbGVnZW5kLmtleS5oZWlnaHQgPSB1bml0KDIuNSwgJ2NtJyksCiAgICAgICAgbGVnZW5kLmtleS53aWR0aCA9IHVuaXQoNiwgJ2NtJyksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gYygwLjI1LCAwLjA2KSwKICAgICAgICBsZWdlbmQuZGlyZWN0aW9uPSJob3Jpem9udGFsIiwKICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTMyKSwKICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0zMiksCiAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD0ibGlnaHRibHVlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZT0wLjUsIGxpbmV0eXBlPSJzb2xpZCIpKQoKIyBleHBvcnQgcG5nIGZpbGUKb3ZlcmxheV9maWxlX3RlbXAgPC0gIi4uL2RhdGEvZmluZV9ncmlkZGVkX2ZvcmVjYXN0X3RlbXAucG5nIgpwbmcob3ZlcmxheV9maWxlX3RlbXAsIHdpZHRoPWltYWdlX3NpemUkd2lkdGgsIGhlaWdodD1pbWFnZV9zaXplJGhlaWdodCwgdW5pdHM9InB4IiwgYmc9InRyYW5zcGFyZW50IikKcF90ZW1wCmRldi5vZmYoKQoKaW1nIDwtIHBuZzo6cmVhZFBORyhvdmVybGF5X2ZpbGVfdGVtcCkKcGxvdC5uZXcoKQpyYXN0ZXJJbWFnZShpbWcsIDAsMCwxLDEpCmBgYAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KY29sb3JzIDwtIGMoIiMzRDAyMzkiLCIjRkEwMEZDIiwiIzA5MDA3OSIsIiM1RTlERjgiLCIjMkU1RTdGIiwKICAgICAgICAgICAgIiMwNkY5RkIiLCIjMEJGNDBCIiwiIzAwNjEwMyIsIiNGQUZCMDciLCIjRDUwNDA0IiwiIzVBMDMwMyIpCnBfdGVtcCA8LSBnZ3Bsb3Qob3V0LCBhZXMoeCwgeSwgej16KSkgKwogIGdlb21fY29udG91cl9maWxsKGJyZWFrcz1zZXEoLTE2LCA4LCBieT0wLjI1KSkgKwogIGdlb21fY29udG91cihicmVha3M9c2VxKC0xNiwgOCwgYnk9MC4yNSksIGNvbG9yPSJibGFjayIsIHNpemU9MC41KSArIAogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKG5hbWU9IlRlbXAiLCBjb2xvdXJzPWNvbG9ycywgbGltaXRzPWMoLTEyLCA1KSkgKwogIGd1aWRlcygpICsKICB0aGVtZShsZWdlbmQua2V5LnNpemUgPSB1bml0KDEsICJjbSIpLAogICAgICAgIGxlZ2VuZC5rZXkuaGVpZ2h0ID0gdW5pdCgwLjQsICdjbScpLAogICAgICAgIGxlZ2VuZC5rZXkud2lkdGggPSB1bml0KDEsICdjbScpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC4xLCAwLjEpLAogICAgICAgIGxlZ2VuZC5kaXJlY3Rpb249Imhvcml6b250YWwiLAogICAgICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGw9ImxpZ2h0Ymx1ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemU9MC41LCBsaW5ldHlwZT0ic29saWQiKSkKcF90ZW1wCmBgYAoKIyMjIOivu+WFpemjjuWcuuaVsOaNrgoKYGBge3IgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTEyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIGxvYWQgZmluZSBncmlkZGVkIGZvcmVjYXN0CmRhdGFVIDwtIHJldHJpZXZlX21pY2Fwc19tb2RlbF9ncmlkKCJHUkFQRVNfMUtNL1VHUkQvMTBNX0FCT1ZFX0dST1VORC8iLCBmaWx0ZXI9IiouMDAxIikKZGF0YVYgPC0gcmV0cmlldmVfbWljYXBzX21vZGVsX2dyaWQoIkdSQVBFU18xS00vVkdSRC8xME1fQUJPVkVfR1JPVU5ELyIsIGZpbHRlcj0iKi4wMDEiKQoKIyBleHRyYWN0IHJlZ2lvbgpkYXRhVSA8LSBkYXRhVVtsb24gPj0gYmJveCRwMSRsb25nICYgbG9uIDw9IGJib3gkcDIkbG9uZyAmIGxhdCA+PSBiYm94JHAxJGxhdCAmIGxhdCA8PSBiYm94JHAyJGxhdF0KZGF0YVYgPC0gZGF0YVZbbG9uID49IGJib3gkcDEkbG9uZyAmIGxvbiA8PSBiYm94JHAyJGxvbmcgJiBsYXQgPj0gYmJveCRwMSRsYXQgJiBsYXQgPD0gYmJveCRwMiRsYXRdCmRhdGFVViA8LSBtZXJnZShkYXRhVSwgZGF0YVYsIGJ5PWMoImxvbiIsICJsYXQiLCAibGV2IiwgInRpbWUiLCAiaW5pdFRpbWUiLCAiZmhvdXIiKSkKCiMgcGxvdCB3aW5kIHN0cmVhbWxpbmVzCnBfd2luZCA8LSBnZ3Bsb3QoZGF0YVVWLCBhZXMobG9uLCBsYXQpKSArCiAgZ2VvbV9zdHJlYW1saW5lKGFlcyhkeD1kbG9uKGRhdGFVViR2YXIxLngsbGF0KSwgZHk9ZGxhdChkYXRhVVYkdmFyMS55KSwgc2l6ZT0uLnN0ZXAuLiwgCiAgICAgICAgICAgICAgICAgICAgICBhbHBoYT0uLnN0ZXAuLiwgY29sb3I9c3FydCguLmR4Li5eMiArIC4uZHkuLl4yKSksCiAgICAgICAgICAgICAgICAgIEw9MC4wMywgcmVzPTIsIG49MTAsIFM9NSwgbGluZWVuZD0icm91bmQiLCBzaXplPTUsIGFycm93PU5VTEwpICsgCiAgZ2VvbV9hcnJvdyhhZXMoZHggPSBkYXRhVVYkdmFyMS54KjAuOCwgZHkgPSBkYXRhVVYkdmFyMS55KjAuOCksIAogICAgICAgICAgICAgc2tpcCA9IDEsIGNvbG9yID0gImJsYWNrIiwgYXJyb3cubGVuZ3RoPTMsIHNpemUgPSAyLjUpICsKICB2aXJpZGlzOjpzY2FsZV9jb2xvcl92aXJpZGlzKGd1aWRlPSJub25lIikgKwogIHNjYWxlX3NpemUocmFuZ2U9YygwLDMpLCBndWlkZT0ibm9uZSIpICsKICBzY2FsZV9hbHBoYShndWlkZT0ibm9uZSIpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kPWMoMCwwKSkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQ9YygwLDApKSArCiAgdGhlbWUocGFuZWwuc3BhY2luZz1ncmlkOjp1bml0KDAsICJtbSIpLAogICAgICAgIHBsb3QubWFyZ2luPWdyaWQ6OnVuaXQocmVwKC0xLjI1LDQpLCJsaW5lcyIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIpLCAjIGJnIG9mIHRoZSBwYW5lbAogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3IgPSBOQSksICMgYmcgb2YgdGhlIHBsb3QKICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X2JsYW5rKCkpCgojIGV4cG9ydCBwbmcgZmlsZQpvdmVybGF5X2ZpbGVfd2luZCA8LSAiLi4vZGF0YS9maW5lX2dyaWRkZWRfZm9yZWNhc3Rfd2luZC5wbmciCnBuZyhvdmVybGF5X2ZpbGVfd2luZCwgd2lkdGg9aW1hZ2Vfc2l6ZSR3aWR0aCwgaGVpZ2h0PWltYWdlX3NpemUkaGVpZ2h0LCB1bml0cz0icHgiLCBiZz0idHJhbnNwYXJlbnQiKQpwX3dpbmQKZGV2Lm9mZigpCgppbWcgPC0gcG5nOjpyZWFkUE5HKG92ZXJsYXlfZmlsZV93aW5kKQpwbG90Lm5ldygpCnJhc3RlckltYWdlKGltZywgMCwwLDEsMSkKYGBgCgojIyDkuInnu7TlnLDlvaLlj6DliqDliIbmnpAKCuWcqOWcsOW9ouS4iuWPoOWKoOS4iui/sOeUn+aIkOeahOWcsOWbviwg5rCU5rip5YiG5biD5Lul5Y+K6aOO5Zy65rWB57q/5Zu+LgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCm92ZXJsYXlfaW1nIDwtIHBuZzo6cmVhZFBORyhvdmVybGF5X2ZpbGUpCm92ZXJsYXlfaW1nX3RlbXAgPC0gcG5nOjpyZWFkUE5HKG92ZXJsYXlfZmlsZV90ZW1wKQpvdmVybGF5X2ltZ193aW5kIDwtIHBuZzo6cmVhZFBORyhvdmVybGF5X2ZpbGVfd2luZCkKCiMgMkQgcGxvdCB3aXRoIG1hcCBvdmVybGF5CnNoYWRlbWF0ICU+JQogIGFkZF9vdmVybGF5KG92ZXJsYXlfaW1nLCBhbHBoYWxheWVyID0gMC45NSkgJT4lCiAgYWRkX292ZXJsYXkob3ZlcmxheV9pbWdfdGVtcCwgYWxwaGFsYXllciA9IDAuNjUpICU+JQogIGFkZF9vdmVybGF5KG92ZXJsYXlfaW1nX3dpbmQsIGFscGhhbGF5ZXIgPSAwLjk1KSAlPiUKICBhZGRfc2hhZG93KHJheW1hdCwgbWF4X2RhcmtlbiA9IDAuNCkgJT4lCiAgYWRkX3NoYWRvdyhhbWJtYXQsIG1heF9kYXJrZW4gPSAwLjQpICU+JQogIHBsb3RfbWFwKCkKYGBgCgrmmL7npLrkuInnu7TlnLDlvaLlm74uCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9MTV9CnpzY2FsZSA8LSAxMAoKcmdsOjpjbGVhcjNkKCkKc2hhZGVtYXQgJT4lIAogIGFkZF9vdmVybGF5KG92ZXJsYXlfaW1nLCBhbHBoYWxheWVyID0gMC45NSkgJT4lCiAgYWRkX292ZXJsYXkob3ZlcmxheV9pbWdfdGVtcCwgYWxwaGFsYXllciA9IDAuNikgJT4lCiAgYWRkX292ZXJsYXkob3ZlcmxheV9pbWdfd2luZCwgYWxwaGFsYXllciA9IDAuOCkgJT4lCiAgYWRkX3NoYWRvdyhyYXltYXQsIG1heF9kYXJrZW4gPSAwLjQpICU+JQogIGFkZF9zaGFkb3coYW1ibWF0LCBtYXhfZGFya2VuID0gMC40KSAlPiUKICBwbG90XzNkKGVsZXZfbWF0cml4LCB6c2NhbGUgPSB6c2NhbGUsIHdpbmRvd3NpemUgPSBjKDEwMDAsIDEwMDApLCBiYWNrZ3JvdW5kID0gIndoaXRlIiwgCiAgICAgICAgICB3YXRlciA9IEZBTFNFLCBzb2xpZGRlcHRoID0gLTE1MCwgd2F0ZXJhbHBoYSA9IDAsCiAgICAgICAgICB0aGV0YSA9IDI1LCBwaGkgPSAzMCwgem9vbSA9IDAuNiwgZm92ID0gNjApCgpsYWJlbCA8LSBsaXN0KHRleHQ9Ik9seW1waWMgU3RhZGl1bSIpCmxhYmVsJHBvcyA8LSBmaW5kX2ltYWdlX2Nvb3JkaW5hdGVzKGxvbmc9MTE2LjM5MjAwLCBsYXQ9MzkuOTkxNTAsIGJib3g9YmJveCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGltYWdlX3dpZHRoPWRpbShlbGV2X21hdHJpeClbMV0sIGltYWdlX2hlaWdodD1kaW0oZWxldl9tYXRyaXgpWzJdKQpyZW5kZXJfbGFiZWwoZWxldl9tYXRyaXgsIHggPSBsYWJlbCRwb3MkeCwgeSA9IGxhYmVsJHBvcyR5LCB6ID0gNjAwMCwKICAgICAgICAgICAgIHpzY2FsZSA9IHpzY2FsZSwgdGV4dCA9IGxhYmVsJHRleHQsIHRleHRzaXplPTEuMiwgbGluZXdpZHRoID0gNSwgZnJlZXR5cGU9VFJVRSkKZGlzdCA9IGRpc3RtKGMoZXh0ZW50KGVsZXZfaW1nKVsxXSwgZXh0ZW50KGVsZXZfaW1nKVszXSksIAogICAgICAgICAgICAgYyhleHRlbnQoZWxldl9pbWcpWzFdLCBleHRlbnQoZWxldl9pbWcpWzRdKSwgZnVuID0gZGlzdEhhdmVyc2luZSlbLDFdCnJlbmRlcl9zY2FsZWJhcihsaW1pdHM9cm91bmQoZGlzdC8xMDAwLiwxKSxsYWJlbF91bml0ID0gJ2ttJykKcmVuZGVyX2NvbXBhc3MocG9zaXRpb24gPSAiTiIsY29tcGFzc19yYWRpdXM9MTIwLCBzY2FsZV9kaXN0YW5jZSA9IDEuMikgCgpyZ2w6OnBhcjNkKHdpbmRvd1JlY3Q9YygtNDUwLCAtMTAwLCAxMDAwLCAxMTAwKSkKcmdsOjp2aWV3M2QodGhldGE9MjAsIHBoaT0zMCwgZm92PTQ1LCB6b29tPTAuNikKClN5cy5zbGVlcCgzKQpyZW5kZXJfc25hcHNob3QoJ2RhdGEvd2ludGVyX29seW1waWMvb3V0cHV0X2ltYWdlLnBuZycsIHRpdGxlX3RleHQgPSAiMjAyMuWMl+S6rOWGrOWlpeeyvue7huWMlumihOaKpSIsCiAgICAgICAgICAgICAgICB0aXRsZV9zaXplID0gNTAsIHRpdGxlX2NvbG9yID0gImJsYWNrIiwgdGl0bGVfZm9udCA9ICJTaW1IZWkiKQojIHJnbDo6cmdsLmNsb3NlKCkKYGBgCg==